package org.sercho.masp.distribution;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.sercho.masp.distribution.context.ContextConnection;
import org.sercho.masp.distribution.ui.UIElement;
import org.sercho.masp.distribution.ui.UserInterfaceMask;
import org.sercho.masp.models.Context.Environment;
import org.sercho.masp.models.Context.InteractionResource;
import org.sercho.masp.models.Context.User;
import org.sercho.masp.notification.NotificationSystem;
import org.sercho.masp.notification.MessageCenter.Message;
import org.sercho.masp.notification.MessageCenter.MessageCenter;
import org.sercho.masp.notification.MessageCenter.MessageCenter.Observer;
import org.sercho.masp.notification.MessageCenter.Urgency;
import org.sercho.masp.notification.decision.DecisionResult;
import org.sercho.masp.notification.test.NotificationTestServlet;

/**
 * The NotificationStrategy implements the {@link DistributionStrategy}
 * -interface. It creates the {@link MessageCenter}s for all known {@link User}
 * s. It's main-task is the calculation of a strategy to distribute
 * notifications on different devices.
 * 
 * @author Benny
 * 
 */
public class NotificationStrategy implements DistributionStrategy {

	/**
	 * Holds a reference to a {@link ContextConnection}-object, which provides
	 * helper-methods for accessing the context-model.
	 */
	private ContextConnection ctxCon;
	/**
	 * Holds a reference to an {@link Environment}-object, which is the
	 * entry-point to the context-model.
	 */
	@SuppressWarnings("unused")
	private Environment env;
	/**
	 * Holds a reference to the {@link NotificationSystem}-instance, to which
	 * the NotificationStrategy delegates the calculation of the
	 * presentation-mode and the devices to use.
	 */
	private NotificationSystem notifSys;
	/**
	 * This map stores the last {@link Message} for every {@link User}.
	 */
	private Map<String, Message> messageMap;

	/**
	 * Constructor.
	 */
	public NotificationStrategy() {
		this.notifSys = NotificationSystem.getInstance();
		this.messageMap = new HashMap<String, Message>();
	}

	/**
	 * Calculates the distribution of a notification for a {@link User}. The
	 * {@link User} is stored in an {@link UserInterfaceMask} as well as the
	 * {@link UIElement}s, which belong to the notification.
	 * 
	 * @param userInterfaceMask
	 *            Holds the {@link User} and the {@link UIElement}s of the
	 *            notification.
	 * @return A set of {@link DistributionConfiguration}s. Each
	 *         {@link DistributionConfiguration} consists of an
	 *         {@link UIElement} and an {@link InteractionResource} on which the
	 *         {@link UIElement} should be displayed.
	 */
	@Override
	public Set<DistributionConfiguration> calculateDistribution(
			UserInterfaceMask userInterfaceMask) {
		Set<DistributionConfiguration> distConfSet = new HashSet<DistributionConfiguration>();
		String userId = userInterfaceMask.getUserId();
		Message msg = this.messageMap.get(userId);
		if (msg != null) {
			String appId = msg.getSource();
			String msgCatId = msg.getTitle();
			String msgTime = msg.getBaseURL();
			int urgency = this.getUrgencyLevel(msg.getUrgency());
			Set<UIElement> uiElems = userInterfaceMask.getUIElements();
			DecisionResult decision = this.notifSys.calculatePresentation(
					appId, msgCatId, userId, msgTime, urgency, this.ctxCon);
			List<InteractionResource> irList = decision
					.getInteractionResources();
			// The following code belongs to the demo-case. When the source of
			// the message is our NotificationTestServlet, a new message will be
			// send to the same user. This message contains the calculated
			// presentation-mode and InteractionResources. This way, we can test
			// our NotificationSystem locally when not in the ShowRoom and
			// without opening many browser-tabs for every display-device for
			// checking the selection of displays.
			if (msg.getSource().equalsIgnoreCase(
					NotificationTestServlet.MESSAGE_SOURCE)) {
				int presMode = decision.getPresentationMode();
				String methodDescr = presMode == 1 ? "(1) No notification"
						: presMode == 2 ? "(2) static symbol in peripheral field of view"
								: presMode == 3 ? "(3) dynamic symbol in peripheral field of view"
										: presMode == 4 ? "(4) dynamic symbol in direct field of view and short sound"
												: "(5) dynamic symbol in direct field of view and long sound";
				StringBuffer decisionMsg = new StringBuffer()
						.append("Calculated notification-method: "
								+ methodDescr + "<br/>");
				decisionMsg
						.append(irList.isEmpty() ? "No devices found or chosen!"
								: irList.size() == 1 ? "The following device was chosen:<br>"
										: "The following devices were chosen:<br>");
				for (InteractionResource ir : irList) {
					decisionMsg.append(ir.getId() + "<br>");
				}
				MessageCenter.send("NotificationSystem", "Decision",
						decisionMsg.toString(), userId);
			}
			// The next step is to retrieve a specific display-resource with
			// it's corresponding input-resource for the demo-case. As we want
			// to only act as one specific display, we have to provide this
			// display as the one to display the notification. If we would use
			// the really calculated resources, we wouldn't see the notification
			// as pop-up and thus couldn't give user-feedback on the urgency.
			InteractionResource irDisplay = this.ctxCon
					.getInteractionResource("SHARP_65_DISPLAY");
			InteractionResource irKeyboard = this.ctxCon
					.getInteractionResource("SHARP_65_KEYBOARD");
			// end of the demo-case-only code

			// The following code is part of the real implementation, but was
			// modified for the demo-case. The code for the real implementation
			// is commented as block comments.

			// In the real implementation, we can only create
			// DistributionConfigurations, if we have any InteractionResources.
			/* if (!irList.isEmpty()) { */
			for (UIElement uiElem : uiElems) {
				// We have to to check for all calculated resources, if it is
				// supported for the current UIElement. If so, we create a
				// DistributionConfiguration for that combination. This part may
				// be altered, as there is no way of altering the UIElements at
				// this point in the current version of MASP. This means, the
				// different presentation-modes can not be fully realized, as we
				// have no control over the type of UIElement. Only the
				// InteractionResources itself are chosen on account of the
				// presentation-mode.
				/*
				 * for (InteractionResource ir : irList) { if
				 * (uiElem.getSupportedResources().contains(ir)) {
				 * DistributionConfiguration distrConf = new
				 * DistributionConfiguration( ir, uiElem);
				 * distConfSet.add(distrConf); } }
				 */

				// This is just demo-case-code. The display- and input-resource
				// are connected to the UIElements.
				DistributionConfiguration distrConf = new DistributionConfiguration(
						irDisplay, uiElem);
				distConfSet.add(distrConf);
				distrConf = new DistributionConfiguration(irKeyboard, uiElem);
				distConfSet.add(distrConf);
			}
			/* } */
		}
		return distConfSet;
	}

	/**
	 * Returns the {@link DistributionStrategyMetaInformation} for this
	 * strategy.
	 * 
	 * @return the {@link DistributionStrategyMetaInformation}
	 */
	@Override
	public DistributionStrategyMetaInformation getMetaInformation() {
		return new NotificationStrategyMetaInformation();
	}

	/**
	 * Sets the {@link ContextConnection}, creates the {@link MessageCenter}s
	 * for the {@link User}s and adds a {@link MessageObserver} to them.
	 * 
	 * @param ctxCon
	 *            the {@link ContextConnection}
	 */
	@Override
	public void setContextConnection(ContextConnection ctxCon) {
		this.ctxCon = ctxCon;
		List<User> userList = ctxCon.getUsers();
		MessageObserver msgObs = new MessageObserver();
		for (User user : userList) {
			String userId = user.getId();
			MessageCenter msgCent = MessageCenter.create(userId);
			msgCent.addObserver(msgObs);
		}
	}

	/**
	 * Sets the {@link Environment}.
	 * 
	 * @param ctxModel
	 *            the {@link Environment}
	 */
	@Override
	public void setContextModel(Environment ctxModel) {
		this.env = ctxModel;
	}

	/**
	 * Returns the integer-representation of an {@link Urgency}.
	 * 
	 * @param urgency
	 *            the {@link Urgency}
	 * @return the integer-representation
	 */
	private int getUrgencyLevel(Urgency urgency) {
		if (urgency != null) {
			if (urgency.equals(Urgency.VERY_LOW)) {
				return 1;
			} else if (urgency.equals(Urgency.LOW)) {
				return 2;
			} else if (urgency.equals(Urgency.MIDDLE)) {
				return 3;
			} else if (urgency.equals(Urgency.HIGH)) {
				return 4;
			} else if (urgency.equals(Urgency.VERY_HIGH)) {
				return 5;
			}
		}
		return 3;
	}

	/**
	 * An inner class, which implements the {@link Observer}-interface of the
	 * {@link MessageCenter}. It only acts, when a new {@link Message} was send.
	 * 
	 * @author Benny
	 * 
	 */
	private class MessageObserver implements Observer {

		/**
		 * Not used by us.
		 * 
		 * @see org.sercho.masp.notification.MessageCenter.MessageCenter.Observer#messageDeleted(org.sercho.masp.notification.MessageCenter.Message)
		 */
		@Override
		public void messageDeleted(Message msg) {
			// TODO Auto-generated method stub

		}

		/**
		 * Not used by us.
		 * 
		 * @see org.sercho.masp.notification.MessageCenter.MessageCenter.Observer#messageRead(org.sercho.masp.notification.MessageCenter.Message)
		 */
		@Override
		public void messageRead(Message msg) {
			// TODO Auto-generated method stub

		}

		/**
		 * Not used by us.
		 * 
		 * @see org.sercho.masp.notification.MessageCenter.MessageCenter.Observer#messageUndeleted(org.sercho.masp.notification.MessageCenter.Message)
		 */
		@Override
		public void messageUndeleted(Message msg) {
			// TODO Auto-generated method stub

		}

		/**
		 * Not used by us.
		 * 
		 * @see org.sercho.masp.notification.MessageCenter.MessageCenter.Observer#messageUnread(org.sercho.masp.notification.MessageCenter.Message)
		 */
		@Override
		public void messageUnread(Message msg) {
			// TODO Auto-generated method stub

		}

		/**
		 * Saves the new {@link Message} into the messageMap with the
		 * {@link User}-ID as key.
		 */
		@Override
		public void newMessage(Message msg) {
			messageMap.put(msg.getUser(), msg);
		}

	}

}
